﻿package org.papervision3d.materials;

import org.papervision3d.core.math.NumberUV;
import org.papervision3d.Papervision3D;
import org.papervision3d.core.geom.renderables.Triangle3D;
import org.papervision3d.core.geom.renderables.Vertex3DInstance;
import org.papervision3d.core.log.PaperLogger;
import org.papervision3d.core.material.TriangleMaterial;
import org.papervision3d.core.proto.MaterialObject3D;
import org.papervision3d.core.render.command.RenderTriangle;
import org.papervision3d.core.render.data.RenderSessionData;
import org.papervision3d.core.render.draw.ITriangleDrawer;
import org.papervision3d.materials.utils.PrecisionMode;
import org.papervision3d.materials.utils.RenderRecStorage;

import nme.display.BitmapData;
import nme.display.Graphics;
import nme.geom.Matrix;
import nme.geom.Point;
import nme.geom.Rectangle;
//import nme.utils.Dictionary;	
import nme.ObjectHash;	

/**
* The BitmapMaterial class creates a texture from a BitmapData object.
*
* Materials collect data about how objects appear when rendered.
*
*/
class BitmapMaterial extends TriangleMaterial, implements ITriangleDrawer
{
	
	private static inline var DEFAULT_FOCUS:Float = 200;
	private static var hitRect:Rectangle = new Rectangle();
	
	
	private var renderRecStorage:Array<RenderRecStorage>;
	private var focus:Float;
	private var _precise:Bool;
	private var _precision:Int;
	private var _perPixelPrecision:Int;
	public var minimumRenderSize:Float;
	
	private var _texture :Dynamic;
	
	/**
	 * Indicates if mip mapping is forced.
	 */
	public static var AUTO_MIP_MAPPING :Bool = false;

	/**
	 * Levels of mip mapping to force.
	 */
	public static var MIP_MAP_DEPTH :Float = 8;

	/**
	 * Precision mode indicates how triangles are created for precise texture render.
	 */
	public var precisionMode:Int;
	
	public var uvMatrices:ObjectHash<Dynamic, Dynamic>;
	
	/**
	* @private
	*/
	private static var _triMatrix:Matrix = new Matrix();
	private static var _triMap:Matrix;
	
	/**
	* @private
	*/
	private static var _localMatrix:Matrix = new Matrix();
	
	/**
	* The BitmapMaterial class creates a texture from a BitmapData object.
	*
	* @param	asset				A BitmapData object.
	*/
	public function new( asset:BitmapData=null, precise:Bool = false)
	{
		super();
		
		focus = 200;
		_precision = 8;
		_perPixelPrecision = 8;
		minimumRenderSize = 4;
		precisionMode = PrecisionMode.ORIGINAL;
		tempTriangleMatrix = new Matrix();
	
		uvMatrices = new ObjectHash<Dynamic, Dynamic>();
		
		// texture calls createBitmap. That's where all the init happens. This allows to reinit when changing texture. -C4RL05
		// if we have an asset passed in, this means we're the subclass, not the super.  Set the texture, let the fun begin.
		if( asset != null) texture = asset;
		this.precise = precise;
		createRenderRecStorage();
	}
	
	private function createRenderRecStorage():Void
	{
		this.renderRecStorage = new Array();
		for(a in 0...101){
			this.renderRecStorage[a] = new RenderRecStorage();
		}	
	}
	
	/**
	* Resets the mapping coordinates. Use when the texture has been resized.
	*/
	public function resetMapping():Void
	{
		uvMatrices = new ObjectHash<Dynamic, Dynamic>();
	}
	
		
	//Local storage. Avoid var's in high usage functions.
	private var x0:Float;
	private var y0:Float;
	private var x1:Float;
	private var y1:Float;
	private var x2:Float;
	private var y2:Float;
	/**
	 *  drawTriangle
	 */
	override public function drawTriangle(tri:RenderTriangle, graphics:Graphics, renderSessionData:RenderSessionData, altBitmap:BitmapData = null, altUV:Matrix = null):Void
	{
	//	trace("at drawing triangle???");
		_triMap = altUV != null ? altUV : (uvMatrices.get(tri) != null? uvMatrices.get(tri) : transformUVRT(tri));
		if(!_precise || _triMap == null){
			if( lineAlpha != 0 )
				graphics.lineStyle( lineThickness, lineColor, lineAlpha );
			if( bitmap != null)
			{
				
				x0 = tri.v0.x;
				y0 = tri.v0.y;
				x1 = tri.v1.x;
				y1 = tri.v1.y;
				x2 = tri.v2.x;
				y2 = tri.v2.y;

				_triMatrix.a = x1 - x0;
				_triMatrix.b = y1 - y0;
				_triMatrix.c = x2 - x0;
				_triMatrix.d = y2 - y0;
				_triMatrix.tx = x0;
				_triMatrix.ty = y0;
					
				_localMatrix.a = _triMap.a;
				_localMatrix.b = _triMap.b;
				_localMatrix.c = _triMap.c;
				_localMatrix.d = _triMap.d;
				_localMatrix.tx = _triMap.tx;
				_localMatrix.ty = _triMap.ty;
				_localMatrix.concat(_triMatrix);
				
				graphics.beginBitmapFill( altBitmap != null ? altBitmap : bitmap, _localMatrix, tiled, smooth);
			}
			graphics.moveTo( x0, y0 );
			graphics.lineTo( x1, y1 );
			graphics.lineTo( x2, y2 );
			graphics.lineTo( x0, y0 );
			if( bitmap != null)
				graphics.endFill();
			if( lineAlpha != 0)
				graphics.lineStyle();
			renderSessionData.renderStatistics.triangles++;
		}else{
			if(bitmap != null){
				focus = renderSessionData.camera.focus;
				tempPreBmp = altBitmap != null? altBitmap : bitmap;
				tempPreRSD = renderSessionData;
				tempPreGrp = graphics;
				cullRect = renderSessionData.viewPort.cullingRectangle;
				renderRec(_triMap, tri.v0, tri.v1, tri.v2, 0);	 
			}
		}
	}
	
	/**
	* Applies the updated UV texture mapping values to the triangle. This is required to speed up rendering.
	*
	*/
	public function transformUV(face3D:Triangle3D):Matrix
	{			
		var mapping:Matrix = null;
		if( face3D.uv == null )
		{
			PaperLogger.error( "MaterialObject3D: transformUV() uv not found!" );
		}
		else if( bitmap != null)
		{
			var uv :Array<NumberUV>  = face3D.uv;
			
			var w  :Float = bitmap.width * maxU;
			var h  :Float = bitmap.height * maxV;
			var u0 :Float = w * face3D.uv0.u;
			var v0 :Float = h * ( 1 - face3D.uv0.v );
			var u1 :Float = w * face3D.uv1.u;
			var v1 :Float = h * ( 1 - face3D.uv1.v);
			var u2 :Float = w * face3D.uv2.u;
			var v2 :Float = h * ( 1 - face3D.uv2.v );
			
			// Fix perpendicular projections
			if( (u0 == u1 && v0 == v1) || (u0 == u2 && v0 == v2) )
			{
				u0 -= (u0 > 0.05)? 0.05 : -0.05;
				v0 -= (v0 > 0.07)? 0.07 : -0.07;
			}
			
			if( u2 == u1 && v2 == v1 )
			{
				u2 -= (u2 > 0.05)? 0.04 : -0.04;
				v2 -= (v2 > 0.06)? 0.06 : -0.06;
			}
			
			// Precalculate matrix & correct for mip mapping
			var at :Float = ( u1 - u0 );
			var bt :Float = ( v1 - v0 );
			var ct :Float = ( u2 - u0 );
			var dt :Float = ( v2 - v0 );
			
			var m :Matrix = new Matrix( at, bt, ct, dt, u0, v0 );
			// Need to mirror over X-axis when righthanded
			if(Papervision3D.useRIGHTHANDED)
			{
				m.scale(-1, 1);
				m.translate(w, 0);
			}
			m.invert();
			
			uvMatrices.set(face3D, m.clone());
			mapping = uvMatrices.get(face3D);
			mapping.a  = m.a;
			mapping.b  = m.b;
			mapping.c  = m.c;
			mapping.d  = m.d;
			mapping.tx = m.tx;
			mapping.ty = m.ty;
		}
		else PaperLogger.error( "MaterialObject3D: transformUV() material.bitmap not found!" );

		return mapping;
	}
	
	/**
	* Applies the updated UV texture mapping values to the triangle. This is required to speed up rendering.
	*
	*/
	public function transformUVRT(tri:RenderTriangle):Matrix
	{			
		var mapping:Matrix = null;
		if( bitmap != null)
		{
			//var uv :Array  = face3D.uv;
			
			var w  :Float = bitmap.width * maxU;
			var h  :Float = bitmap.height * maxV;
			var u0 :Float = w * tri.uv0.u;
			var v0 :Float = h * ( 1 - tri.uv0.v );
			var u1 :Float = w * tri.uv1.u;
			var v1 :Float = h * ( 1 - tri.uv1.v);
			var u2 :Float = w * tri.uv2.u;
			var v2 :Float = h * ( 1 - tri.uv2.v );
			
			// Fix perpendicular projections
			if( (u0 == u1 && v0 == v1) || (u0 == u2 && v0 == v2) )
			{
				u0 -= (u0 > 0.05)? 0.05 : -0.05;
				v0 -= (v0 > 0.07)? 0.07 : -0.07;
			}
			
			if( u2 == u1 && v2 == v1 )
			{
				u2 -= (u2 > 0.05)? 0.04 : -0.04;
				v2 -= (v2 > 0.06)? 0.06 : -0.06;
			}
			
			// Precalculate matrix & correct for mip mapping
			var at :Float = ( u1 - u0 );
			var bt :Float = ( v1 - v0 );
			var ct :Float = ( u2 - u0 );
			var dt :Float = ( v2 - v0 );
			
			var m :Matrix = new Matrix( at, bt, ct, dt, u0, v0 );
			// Need to mirror over X-axis when righthanded
			if(Papervision3D.useRIGHTHANDED)
			{
				m.scale(-1, 1);
				m.translate(w, 0);
			}
			m.invert();
			uvMatrices.set(tri, m.clone());
			mapping = uvMatrices.get(tri);
			mapping.a  = m.a;
			mapping.b  = m.b;
			mapping.c  = m.c;
			mapping.d  = m.d;
			mapping.tx = m.tx;
			mapping.ty = m.ty;
		}
		else PaperLogger.error( "MaterialObject3D: transformUV() material.bitmap not found!" );

		return mapping;
	}
	
	
	private var ax:Float;
	private var ay:Float;
	private var az:Float;
	private var bx:Float;
	private var by:Float;
	private var bz:Float;
	private var cx:Float;
	private var cy:Float;
	private var cz:Float;
	private var faz:Float;
	private var fbz:Float;
	private var fcz:Float;
	private var mabz:Float;
	private var mbcz:Float;
	private var mcaz:Float;
	private var mabx:Float;
	private var maby:Float;
	private var mbcx:Float;
	private var mbcy:Float;
	private var mcax:Float;
	private var mcay:Float;
	private var dabx:Float;
	private var daby:Float;
	private var dbcx:Float;
	private var dbcy:Float;
	private var dcax:Float;
	private var dcay:Float;
	private var dsab:Float;
	private var dsbc:Float;
	private var dsca:Float;
	private var dmax:Float;
	private var cullRect:Rectangle;
	
	private var tempPreGrp:Graphics;
	private var tempPreBmp:BitmapData;
	private var tempPreRSD:RenderSessionData;
	private var tempTriangleMatrix:Matrix;
	private var a2:Float;
	private var b2:Float;
	private var c2:Float;
	private var d2:Float;

	private var dx:Float;
	private var dy:Float;
	private var d2ab:Float;
	private var  d2bc:Float;
	private var d2ca:Float;
	
	private function renderRec(emMap:Matrix, v0:Vertex3DInstance, v1:Vertex3DInstance, v2:Vertex3DInstance, index:Float):Void
	{
		az = v0.z;
		bz = v1.z;
		cz = v2.z;
		
		//Cull if a vertex behind near.
		if((az <= 0) && (bz <= 0) && (cz <= 0))
			return;
		
		cx = v2.x;
		cy = v2.y;
		bx = v1.x;
		by = v1.y;
		ax = v0.x;
		ay = v0.y;
		
		//Cull if outside of viewport.
		if(cullRect != null){
			hitRect.x = (bx < ax ? (bx < cx ? bx : cx) : (ax < cx ? ax : cx ));
			hitRect.width = (bx > ax ? (bx > cx ? bx : cx) : (ax > cx ? ax : cx )) + (hitRect.x < 0 ? -hitRect.x : hitRect.x);
			hitRect.y = (by < ay ? (by < cy ? by : cy) : (ay < cy ? ay : cy ));
			hitRect.height = (by > ay ? (by > cy ? by : cy) : (ay > cy ? ay : cy )) + (hitRect.y < 0 ? -hitRect.y : hitRect.y);
			if(!((hitRect.right<cullRect.left)||(hitRect.left>cullRect.right))){
				if(!((hitRect.bottom<cullRect.top)||(hitRect.top>cullRect.bottom))){
				
				}else{
					return;
				}
			}else{
				return;
			}
		}
		
		//cull if max iterations is reached, focus is invalid or if tesselation is to small.
		if (index >= 100 || (hitRect.width < minimumRenderSize) || (hitRect.height < minimumRenderSize) || (Math.isFinite(focus)))
		{
			
			//Draw this triangle.
			a2 = v1.x - v0.x;
			b2 = v1.y - v0.y;
			c2 = v2.x - v0.x;
			d2 = v2.y - v0.y;
					
			tempTriangleMatrix.a = emMap.a*a2 + emMap.b*c2;
			tempTriangleMatrix.b = emMap.a*b2 + emMap.b*d2;
			tempTriangleMatrix.c = emMap.c*a2 + emMap.d*c2;
			tempTriangleMatrix.d = emMap.c*b2 + emMap.d*d2;
			tempTriangleMatrix.tx = emMap.tx*a2 + emMap.ty*c2 + v0.x;   
			tempTriangleMatrix.ty = emMap.tx*b2 + emMap.ty*d2 + v0.y;       
			
			if(lineAlpha != 0){
				tempPreGrp.lineStyle( lineThickness, lineColor, lineAlpha );
			}
			tempPreGrp.beginBitmapFill(tempPreBmp, tempTriangleMatrix, tiled, smooth);
			tempPreGrp.moveTo(v0.x, v0.y);
			tempPreGrp.lineTo(v1.x, v1.y);
			tempPreGrp.lineTo(v2.x, v2.y);
			tempPreGrp.endFill();
			if(lineAlpha != 0){
				tempPreGrp.lineStyle();
			}
			
			tempPreRSD.renderStatistics.triangles++;
			return;
		}
		
		faz = focus + az;
		fbz = focus + bz;
		fcz = focus + cz;
		mabz = 2 / (faz + fbz);
		mbcz = 2 / (fbz + fcz);
		mcaz = 2 / (fcz + faz);
		mabx = (ax*faz + bx*fbz)*mabz;
		maby = (ay*faz + by*fbz)*mabz;
		mbcx = (bx*fbz + cx*fcz)*mbcz;
		mbcy = (by*fbz + cy*fcz)*mbcz;
		mcax = (cx*fcz + ax*faz)*mcaz;
		mcay = (cy*fcz + ay*faz)*mcaz;
		dabx = ax + bx - mabx;
		daby = ay + by - maby;
		dbcx = bx + cx - mbcx;
		dbcy = by + cy - mbcy;
		dcax = cx + ax - mcax;
		dcay = cy + ay - mcay;
		dsab = (dabx*dabx + daby*daby);
		dsbc = (dbcx*dbcx + dbcy*dbcy);
		dsca = (dcax*dcax + dcay*dcay);
		
		var nIndex:Int = Std.int(index + 1);
		var nRss:RenderRecStorage = untyped renderRecStorage[Std.int(index)];
		var renderRecMap:Matrix = nRss.mat;
		
		if ((dsab <= _precision) && (dsca <= _precision) && (dsbc <= _precision)){
		   //Draw this triangle.
		   a2 = v1.x - v0.x;
		   b2 = v1.y - v0.y;
		   c2 = v2.x - v0.x;
		   d2 = v2.y - v0.y;
					
			tempTriangleMatrix.a = emMap.a*a2 + emMap.b*c2;
			tempTriangleMatrix.b = emMap.a*b2 + emMap.b*d2;
			tempTriangleMatrix.c = emMap.c*a2 + emMap.d*c2;
			tempTriangleMatrix.d = emMap.c*b2 + emMap.d*d2;
			tempTriangleMatrix.tx = emMap.tx*a2 + emMap.ty*c2 + v0.x;   
			tempTriangleMatrix.ty = emMap.tx*b2 + emMap.ty*d2 + v0.y;       
			
			if(lineAlpha != 0){
				tempPreGrp.lineStyle( lineThickness, lineColor, lineAlpha );
			}
			tempPreGrp.beginBitmapFill(tempPreBmp, tempTriangleMatrix, tiled, smooth);
			tempPreGrp.moveTo(v0.x, v0.y);
			tempPreGrp.lineTo(v1.x, v1.y);
			tempPreGrp.lineTo(v2.x, v2.y);
			tempPreGrp.endFill();
			if(lineAlpha != 0){
				tempPreGrp.lineStyle();
			}
		   
		   
		   tempPreRSD.renderStatistics.triangles++;
		   return;
		}
		
		if ((dsab > _precision) && (dsca > _precision) && (dsbc > _precision)){
			renderRecMap.a = emMap.a*2;
			renderRecMap.b = emMap.b*2;
			renderRecMap.c = emMap.c*2;
			renderRecMap.d = emMap.d*2;
			renderRecMap.tx = emMap.tx*2;
			renderRecMap.ty = emMap.ty*2;
					
			nRss.v0.x = mabx * 0.5;
			nRss.v0.y = maby * 0.5;
			nRss.v0.z = (az+bz) * 0.5;
			
			nRss.v1.x = mbcx * 0.5;
			nRss.v1.y = mbcy * 0.5;
			nRss.v1.z = (bz+cz) * 0.5;
			
			nRss.v2.x = mcax * 0.5;
			nRss.v2.y = mcay * 0.5;
			nRss.v2.z = (cz+az) * 0.5;
			renderRec(renderRecMap, v0, nRss.v0, nRss.v2, nIndex);
			
			renderRecMap.tx -=1;
			renderRec(renderRecMap, nRss.v0, v1, nRss.v1, nIndex);
			
			renderRecMap.ty -=1;
			renderRecMap.tx = emMap.tx*2;
			renderRec(renderRecMap, nRss.v2, nRss.v1, v2, nIndex);
			
			renderRecMap.a = -emMap.a*2;
			renderRecMap.b = -emMap.b*2;
			renderRecMap.c = -emMap.c*2;
			renderRecMap.d = -emMap.d*2;
			renderRecMap.tx = -emMap.tx*2+1;
			renderRecMap.ty = -emMap.ty*2+1;
			renderRec(renderRecMap, nRss.v1, nRss.v2, nRss.v0, nIndex);

			return;
		}

		if( precisionMode == PrecisionMode.ORIGINAL )
		{
			d2ab = dsab;
			d2bc = dsbc;
			d2ca = dsca;
			dmax = (dsca > dsbc ? (dsca > dsab ? dsca : dsab) : (dsbc > dsab ? dsbc : dsab ));
		}
		else
		{
			// Calculate best tessellation edge
			dx = v0.x - v1.x;
			dy = v0.y - v1.y;
			d2ab = dx * dx + dy * dy;
			
			dx = v1.x - v2.x;
			dy = v1.y - v2.y;
			d2bc = dx * dx + dy * dy;
			
			dx = v2.x - v0.x;
			dy = v2.y - v0.y;
			d2ca = dx * dx + dy * dy;
		
			dmax = (d2ca > d2bc ? (d2ca > d2ab ? d2ca : d2ab) : (d2bc > d2ab ? d2bc : d2ab ));		// dmax = Math.max( d2ab, d2bc, d2ac );
		}

		// Break triangle along edge
		if (d2ab == dmax)
		{
			renderRecMap.a = emMap.a*2;
			renderRecMap.b = emMap.b;
			renderRecMap.c = emMap.c*2;
			renderRecMap.d = emMap.d;
			renderRecMap.tx = emMap.tx*2;
			renderRecMap.ty = emMap.ty;
			nRss.v0.x = mabx * 0.5;
			nRss.v0.y = maby * 0.5;
			nRss.v0.z = (az+bz) * 0.5;
			renderRec(renderRecMap, v0, nRss.v0, v2, nIndex);
			
			renderRecMap.a = emMap.a*2+emMap.b;
			renderRecMap.c = 2*emMap.c+emMap.d;
			renderRecMap.tx = emMap.tx*2+emMap.ty-1;
			renderRec(renderRecMap, nRss.v0, v1, v2, nIndex);
		
			return;
		}

		if (d2ca == dmax){
			
			renderRecMap.a = emMap.a;
			renderRecMap.b = emMap.b*2;
			renderRecMap.c = emMap.c;
			renderRecMap.d = emMap.d*2;
			renderRecMap.tx = emMap.tx;
			renderRecMap.ty = emMap.ty*2;
			nRss.v2.x = mcax * 0.5;
			nRss.v2.y = mcay * 0.5;
			nRss.v2.z = (cz+az) * 0.5;
			renderRec(renderRecMap, v0, v1, nRss.v2, nIndex);
			
			renderRecMap.b += emMap.a;
			renderRecMap.d += emMap.c;
			renderRecMap.ty += emMap.tx-1;
			renderRec(renderRecMap, nRss.v2, v1, v2, nIndex);
			
			return;
		}
		renderRecMap.a = emMap.a-emMap.b;
		renderRecMap.b = emMap.b*2;
		renderRecMap.c = emMap.c-emMap.d;
		renderRecMap.d = emMap.d*2;
		renderRecMap.tx = emMap.tx-emMap.ty;
		renderRecMap.ty = emMap.ty*2;
		
		nRss.v1.x = mbcx * 0.5;
		nRss.v1.y = mbcy * 0.5;
		nRss.v1.z = (bz+cz)*0.5;
		renderRec(renderRecMap, v0, v1, nRss.v1, nIndex);
		
		renderRecMap.a = emMap.a*2;
		renderRecMap.b = emMap.b-emMap.a;
		renderRecMap.c = emMap.c*2;
		renderRecMap.d = emMap.d-emMap.c;
		renderRecMap.tx = emMap.tx*2;
		renderRecMap.ty = emMap.ty-emMap.tx;
		renderRec(renderRecMap, v0, nRss.v1, v2, nIndex);
	}

	/**
	* Returns a string value representing the material properties in the specified BitmapMaterial object.
	*
	* @return	A string.
	*/
	public override function toString(): String
	{
		return 'Texture:' + this.texture + ' lineColor:' + this.lineColor + ' lineAlpha:' + this.lineAlpha;
	}


	// ______________________________________________________________________ CREATE BITMAP

	private function createBitmap( asset:BitmapData ):BitmapData
	{		
		resetMapping();

		var bm:BitmapData;
		
		if( AUTO_MIP_MAPPING )
		{
			bm = correctBitmap( asset );
		}
		else
		{
			this.maxU = this.maxV = 1;

			bm = asset;
		}
		
		return bm;
	}


	// ______________________________________________________________________ CORRECT BITMAP FOR MIP MAPPING

	private function correctBitmap( bitmap :BitmapData ):BitmapData
	{
		var okBitmap :BitmapData;

		var levels :Float = 1 << Std.int(MIP_MAP_DEPTH);
		// this is faster than Math.ceil
		var bWidth :Float = bitmap.width  / levels;
		bWidth = Std.int(bWidth) == Std.int(bWidth)  ? bWidth : Std.int(bWidth)+1;
		var bHeight :Float = bitmap.height  / levels;
		bHeight = Std.int(bHeight) == Std.int(bHeight) ? bHeight : Std.int(bHeight)+1;
		
		var width  :Float = levels * bWidth;
		var height :Float = levels * bHeight;

		// Check for BitmapData maximum size
		var ok:Bool = true;

		if( width  > 2880 )
		{
			width  = bitmap.width;
			ok = false;
		}

		if( height > 2880 )
		{
			height = bitmap.height;
			ok = false;
		}
		
		if( ! ok ) PaperLogger.warning( "Material " + this.name + ": Texture too big for mip mapping. Resizing recommended for better performance and quality." );

		// Create new bitmap?
		if( bitmap != null && ( bitmap.width % levels !=0  ||  bitmap.height % levels != 0 ) )
		{
			okBitmap = new BitmapData( Std.int(width), Std.int(height), bitmap.transparent, 0x00000000 );

				
			// this is for ISM and offsetting bitmaps that have been resized
			widthOffset = bitmap.width;
			heightOffset = bitmap.height;
			
			this.maxU = bitmap.width / width;
			this.maxV = bitmap.height / height;

			okBitmap.draw( bitmap );

			// PLEASE DO NOT REMOVE
			extendBitmapEdges( okBitmap, bitmap.width, bitmap.height );
		}
		else
		{
			this.maxU = this.maxV = 1;

			okBitmap = bitmap;
		}

		return okBitmap;
	}

	private function extendBitmapEdges( bmp:BitmapData, originalWidth:Float, originalHeight:Float ):Void
	{
		var srcRect  :Rectangle = new Rectangle();
		var dstPoint :Point = new Point();
		
		var i        :Int;

		// Check width
		if( bmp.width > originalWidth )
		{
			// Extend width
			srcRect.x      = originalWidth-1;
			srcRect.y      = 0;
			srcRect.width  = 1;
			srcRect.height = originalHeight;
			dstPoint.y     = 0;
			
			i = Std.int(originalWidth);
			while(i < bmp.width)
			{
				dstPoint.x = i;
				bmp.copyPixels( bmp, srcRect, dstPoint );
				i++;
			}
		}

		// Check height
		if( bmp.height > originalHeight )
		{
			// Extend height
			srcRect.x      = 0;
			srcRect.y      = originalHeight-1;
			srcRect.width  = bmp.width;
			srcRect.height = 1;
			dstPoint.x     = 0;
			i = Std.int(originalHeight);
			
			while(i < bmp.height)
			{
				dstPoint.y = i;
				bmp.copyPixels( bmp, srcRect, dstPoint );
				i++;
			}
		}
	}

	// ______________________________________________________________________
	
	
	/**
	 * resetUVMatrices();
	 * 
	 * Resets the precalculated uvmatrices, so they can be recalculated
	 */
	 public function resetUVS():Void
	 {
		uvMatrices = new ObjectHash<Dynamic, Dynamic>();
	 }
	
	/**
	* Copies the properties of a material.
	*
	* @param	material	Material to copy from.
	*/
	override public function copy( material :MaterialObject3D ):Void
	{
		super.copy( material );

		this.maxU = material.maxU;
		this.maxV = material.maxV;
	}

	/**
	* Creates a copy of the material.
	*
	* @return	A newly created material that contains the same properties.
	*/
	override public function clone():MaterialObject3D
	{
		var cloned:MaterialObject3D = super.clone();

		cloned.maxU = this.maxU;
		cloned.maxV = this.maxV;

		return cloned;
	}
	
	/**
	 * Sets the material's precise rendering mode. If set to true, material will adaptively render triangles to conquer texture distortion. 
	 */
	public var precise(get_precise, set_precise):Bool;
	private function set_precise(boolean:Bool):Bool
	{
		_precise = boolean;
		return _precise;
	}
	
	private function get_precise():Bool
	{
		return _precise;
	}
	
	/**
	 * If the material is rendering with @see precise to true, this sets tesselation per pixel ratio.
	 */
	public var precision(get_precision, set_precision):Int;
	private function set_precision(precision:Int):Int
	{
		_precision = precision;
		return _precision;
	}
	
	private function get_precision():Int
	{
		return _precision;
	}
	
	/**
	 * If the material is rendering with @see precise to true, this sets tesselation per pixel ratio.
	 * 
	 * corrected to set per pixel precision exactly.
	 */
	public var pixelPrecision(get_pixelPrecision, set_pixelPrecision):Int;
	private function set_pixelPrecision(precision:Int):Int
	{
		_precision = Std.int(precision*precision*1.4);
		_perPixelPrecision = precision;
		return _precision;
	}
	
	private function get_pixelPrecision():Int
	{
		return _perPixelPrecision;
	}
	
	/**
	* A texture object.
	*/		
	public var texture(get_texture, set_texture):Dynamic;
	private function get_texture():Dynamic
	{
		return this._texture;
	}
	
	/**
	* @private
	*/
	private function set_texture( asset:Dynamic ):Dynamic
	{
		if( Std.is(asset, BitmapData) == false )
		{
			PaperLogger.error("BitmapMaterial.texture requires a BitmapData object for the texture");
			return;
		}
		
		bitmap   = createBitmap( untyped (asset) );	
		
		_texture = asset;
		
		return _texture;
	}
	
	override public function destroy():Void
	{
		super.destroy();
		if(uvMatrices != null){
			uvMatrices = null;
		}
		if(bitmap != null){
			bitmap.dispose();
		}
		this.renderRecStorage = null;	
	}
		
}
